/*
	This file is part of PropExtractor.

	AliceDecrypt is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	AliceDecrypt is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with PropExtractor. If not, see <http://www.gnu.org/licenses/>.
*/

#include "PEAliceSoftModel.h"

#include "PETools.h"

namespace pe
{

	int AliceZippedModelReadHeader(AliceModelZippedHeader* a_Target, PEFile* a_File)
	{
		fread(a_Target, sizeof(AliceModelZippedHeader), 1, a_File->handle);

		if (a_Target->header[0] != 'Z' ||
			a_Target->header[1] != 'L' ||
			a_Target->header[2] != 'B')
		{
			return -1;
		}

		return 0;
	}

	int AliceZippedModelDecompress(AliceModelData* a_Target, AliceModelZippedHeader* a_Header, PEFile* a_File)
	{
		a_Target->datasize = a_Header->datasize;
		a_Target->data = new byte[a_Target->datasize];
		a_Target->handle = a_File;

		z_stream zlib_stream;
		zlib_stream.zalloc = Z_NULL;
		zlib_stream.zfree = Z_NULL;
		zlib_stream.opaque = Z_NULL;
		zlib_stream.avail_in = 0;
		zlib_stream.next_in = Z_NULL;

		inflateInit(&zlib_stream);

		byte* dst = a_Target->data;
		unsigned int have;
		int ret;
		unsigned char in[CHUNK];
		unsigned char out[CHUNK];
		unsigned int total_read = 0;

		do 
		{
			zlib_stream.avail_in = fread(in, 1, CHUNK, a_Target->handle->handle);
			if (ferror(a_Target->handle->handle)) 
			{
				(void)inflateEnd(&zlib_stream);
				return Z_ERRNO;
			}
			if (zlib_stream.avail_in == 0) 
			{ 
				break; 
			}
			zlib_stream.next_in = in;

			do 
			{
				zlib_stream.avail_out = CHUNK;
				zlib_stream.next_out = out;
				ret = inflate(&zlib_stream, Z_NO_FLUSH);
				switch (ret) 
				{
				case Z_NEED_DICT:
					ret = Z_DATA_ERROR;					

				case Z_DATA_ERROR:
				case Z_MEM_ERROR:
					(void)inflateEnd(&zlib_stream);
					return ret;
				}

				have = CHUNK - zlib_stream.avail_out;
				total_read += have;
				memcpy(dst, out, have);
				dst += have;
			}
			while (zlib_stream.avail_out == 0);
		} 
		while (ret != Z_STREAM_END);

		return 0;
	}

	int AliceModelSaveToFile(AliceModelData* a_Data, const char* a_FilePath, const char* a_Pattern)
	{
		char path[_MAX_PATH];
		size_t path_len;
		if (a_Pattern)
		{
			GetPatternedPath(path, &path_len, a_Pattern, a_FilePath, NULL, 0);
		}
		else
		{
			GetPatternedPath(path, &path_len, "%workingdir%%name%%ext%", a_FilePath, NULL, 0);
		}

		FILE* write_file = fopen(path, "w+b");
		fwrite(a_Data->data, a_Data->datasize, 1, write_file);
		fclose(write_file);

		return 0;
	}

	int AliceModelDecrypt(AliceModel* a_Target, AliceModelData* a_Data)
	{
		a_Target->header = new AliceModelHeader;
		a_Data->data += ReadBytes((byte*)a_Target->header, sizeof(AliceModelHeader), 1, a_Data->data);

		char* dst_write;
		char c;

		dword counter = a_Target->header->mesh_count;

		a_Target->material = new AliceMaterial[a_Target->header->mesh_count];
		a_Target->material_total = a_Target->header->mesh_count;

		a_Target->mesh = new AliceMesh[a_Target->header->mesh_count];
		a_Target->mesh_total = a_Target->header->mesh_count;

		AliceMaterial* dst_mat = a_Target->material;

		char name[256];
		char texturename[256];
		while (counter > 0)
		{
			ALICE_DEBUG("Meshes left: %i", counter);

			// name

			memset(name, 0, 256);
			dst_write = name;
			do 
			{
				a_Data->data += ReadBytes(&c, 1, 1, a_Data->data);
				*dst_write++ = c;
			} 
			while (c != 0);

			dst_mat->name = new char[strlen(name) + 1];
			strcpy(dst_mat->name, name);

			ALICE_DEBUG("Name: %s", name);

			dword something1;
			a_Data->data += ReadBytes(&something1, 4, 1, a_Data->data);

			// texture

			memset(texturename, 0, 256);
			dst_write = texturename;
			do 
			{
				a_Data->data += ReadBytes(&c, 1, 1, a_Data->data);
				*dst_write++ = c;
			} 
			while (c != 0);

			ALICE_DEBUG("Texture: %s", texturename);

			dst_mat->texture = new char[strlen(texturename) + 1];
			strcpy(dst_mat->texture, texturename);

			dword something2;
			a_Data->data += ReadBytes(&something2, 4, 1, a_Data->data);

			dword padding;
			a_Data->data += ReadBytes(&padding, 4, 1, a_Data->data);

			counter--;

			dst_mat++;
		}

		dword vertexsize;
		a_Data->data += ReadBytes(&vertexsize, 4, 1, a_Data->data);

		dword padding1;
		a_Data->data += ReadBytes(&padding1, 4, 1, a_Data->data);

		AliceMesh* dst_mesh = a_Target->mesh;

		// mesh

		char meshname[256];
		memset(meshname, 0, 256);
		dst_write = meshname;
		do 
		{
			a_Data->data += ReadBytes(&c, 1, 1, a_Data->data);
			*dst_write++ = c;
		} 
		while (c != 0);

		dst_mesh->name = new char[strlen(meshname) + 1];
		strcpy(dst_mesh->name, meshname);

		dst_mesh->material = &a_Target->material[0];

		dword padding2;
		a_Data->data += ReadBytes(&padding2, 4, 1, a_Data->data);

		a_Data->data += ReadBytes(&dst_mesh->vertex_total, 4, 1, a_Data->data);

		ALICE_DEBUG("Vertex size: %i", vertexsize);

		if (vertexsize == 3)
		{
			ALICE_DEBUG("Vertex total: %i", dst_mesh->vertex_total);

			dst_mesh->vertex_data2 = new AliceVertexData2[dst_mesh->vertex_total];
			for (unsigned int i = 0; i < dst_mesh->vertex_total; i++)
			{
				a_Data->data += ReadBytes(&dst_mesh->vertex_data2[i], sizeof(AliceVertexData2), 1, a_Data->data);
				if (dst_mesh->vertex_data2[i].unknown1 == 1 && dst_mesh->vertex_data2[i].unknown2 != 0)
				{
					dword something;
					a_Data->data += ReadBytes(&something, 4, 1, a_Data->data);
				}
				else
				{
					for (word i = 1; i < dst_mesh->vertex_data2[i].unknown1; i++)
					{
						dword something[2];
						a_Data->data += ReadBytes(&something, 4, 2, a_Data->data);
					}
				}
			}

			a_Data->data += ReadBytes(&dst_mesh->uv_total, 4, 1, a_Data->data);

			ALICE_DEBUG("UV total: %i", dst_mesh->uv_total);

			dst_mesh->uv_data1 = new AliceUVData1[dst_mesh->uv_total];
			for (unsigned int i = 0; i < dst_mesh->uv_total; i++)
			{
				a_Data->data += ReadBytes(&dst_mesh->uv_data1[i], sizeof(AliceUVData1), 1, a_Data->data);
			}

			dword unknown1;
			a_Data->data += ReadBytes(&unknown1, 4, 1, a_Data->data);

			dword unknown2;
			a_Data->data += ReadBytes(&unknown2, 4, 1, a_Data->data);

			dword unknown3;
			a_Data->data += ReadBytes(&unknown3, 4, 1, a_Data->data);

			a_Data->data += ReadBytes(&dst_mesh->face_total, 4, 1, a_Data->data);

			ALICE_DEBUG("Face total: %i", dst_mesh->face_total);

			dst_mesh->face_data = new AliceFaceData[dst_mesh->face_total];
			for (unsigned int i = 0; i < dst_mesh->face_total; i++)
			{
				a_Data->data += ReadBytes(&dst_mesh->face_data[i], sizeof(AliceFaceData), 1, a_Data->data);
			}
		}
		else
		{
			ALICE_PRINT("ERROR! Unhandled vertex size: %i", vertexsize);
		}

		return 0;
	}

	int AliceSaveModelToObj(AliceModel* a_Model, const char* a_FilePath)
	{
		char path[_MAX_PATH];
		size_t path_len;

		char* lastslash = strrchr((char*)a_FilePath, '\\');
		if (!lastslash) 
		{ 
			lastslash = (char*)a_FilePath; 
		}
		else
		{
			lastslash++;
		}

		char filename[256];
		memset(filename, 0, 256);
		strncpy(filename, lastslash, strlen(lastslash));

		// mtl

		GetPatternedPath(path, &path_len, "%workingdir%%name%", a_FilePath, NULL, 0);
		strcat(path, ".mtl");

		FILE* write_mtl = fopen(path, "w+");

		fprintf(write_mtl, "# Generated with AliceDecrypt 0.1\n");
		fprintf(write_mtl, "\n");

		for (unsigned int i = 0; i < a_Model->material_total; i++)
		{
			fprintf(write_mtl, "newmtl %s\n", a_Model->material[i].name);
			fprintf(write_mtl, "illum 4\n");
			fprintf(write_mtl, "Kd 1.00 1.00 1.00\n");
			fprintf(write_mtl, "Ka 1.00 1.00 1.00\n");
			fprintf(write_mtl, "Ks 1.00 1.00 1.00\n");
			fprintf(write_mtl, "map_Ka %s.tga\n", a_Model->material[i].texture);
			fprintf(write_mtl, "map_Kd %s.tga\n", a_Model->material[i].texture);
			fprintf(write_mtl, "\n");
		}

		fclose(write_mtl);

		// obj

		GetPatternedPath(path, &path_len, "%workingdir%%name%", a_FilePath, NULL, 0);
		strcat(path, ".obj");

		FILE* write_obj = fopen(path, "w+");

		fprintf(write_obj, "# Generated with AliceDecrypt 0.1\n");
		fprintf(write_obj, "\n");

		fprintf(write_obj, "mtllib %s.mtl\n", filename);
		fprintf(write_obj, "\n");

		for (unsigned int j = 0; j < a_Model->mesh_total; j++)
		{
			fprintf(write_obj, "# Mesh: %s\n", a_Model->mesh[j].name);
			fprintf(write_obj, "\n");

			fprintf(write_obj, "# Vertex count: %i\n", a_Model->mesh[j].vertex_total);
			fprintf(write_obj, "\n");

			for (unsigned int i = 0; i < a_Model->mesh[j].vertex_total; i++)
			{
				fprintf(
					write_obj, "v %f %f %f\n", 
					a_Model->mesh[j].vertex_data2[i].vx, 
					a_Model->mesh[j].vertex_data2[i].vy, 
					a_Model->mesh[j].vertex_data2[i].vz
				);
			}

			fprintf(write_obj, "\n");

			fprintf(write_obj, "# UV count: %i\n", a_Model->mesh[j].uv_total);
			fprintf(write_obj, "\n");

			for (unsigned int i = 0; i < a_Model->mesh[j].uv_total; i++)
			{
				fprintf(
					write_obj, "vt %f %f\n", 
					a_Model->mesh[j].uv_data1[i].x, 
					a_Model->mesh[j].uv_data1[i].y * -1.f
				);
			}

			fprintf(write_obj, "\n");

			fprintf(write_obj, "usemtl %s\n", a_Model->mesh[j].material->name);

			fprintf(write_obj, "# Face count: %i\n", a_Model->mesh[j].face_total);
			fprintf(write_obj, "\n");

			for (unsigned int i = 0; i < a_Model->mesh[j].face_total; i++)
			{
				fprintf(
					write_obj,
					"f %i/%i %i/%i %i/%i\n", 
					a_Model->mesh[j].face_data[i].vertex_index[1] + 1, a_Model->mesh[j].face_data[i].uv_index[1] + 1,
					a_Model->mesh[j].face_data[i].vertex_index[0] + 1, a_Model->mesh[j].face_data[i].uv_index[0] + 1,
					a_Model->mesh[j].face_data[i].vertex_index[2] + 1, a_Model->mesh[j].face_data[i].uv_index[2] + 1
				);
			}
		}

		fclose(write_obj);

		return 0;
	}
	

}; // namespace pe
